#pragma once
#include <optional>
#include <memory>
#include <boost/signals2.hpp>
#include <boost/describe.hpp>
#include <SFML/System/Vector2.hpp>
#include <SFML/Graphics/ConvexShape.hpp>
#include <nice2d/node.h>
#include <nice2d/serializer.h>
#include <nice2d/tile.h>

enum TileDirection {
    TileDirection_0 = 0,
    TileDirection_45 = 45,
    TileDirection_60 = 60,
    TileDirection_90 = 90,
    TileDirection_120 = 120,
    TileDirection_135 = 135,
    TileDirection_180 = 180,
    TileDirection_240 = 240,
    TileDirection_225 = 255,
    TileDirection_270 = 270,
    TileDirection_300 = 300,
    TileDirection_315 = 315,
    TileDirection_Max,
};

BOOST_DESCRIBE_ENUM(TileDirection,
                    TileDirection_0,
                    TileDirection_45,
                    TileDirection_60,
                    TileDirection_90,
                    TileDirection_120,
                    TileDirection_135,
                    TileDirection_180,
                    TileDirection_240,
                    TileDirection_225,
                    TileDirection_270,
                    TileDirection_300,
                    TileDirection_315,
                    TileDirection_Max)

class TileMap : public Node
{
    DECLARE_OBJECT(TileMap)
public:
    static std::shared_ptr<TileMap> create(const std::string &type);
    static std::shared_ptr<TileMap> create(TileMapType type);
    TileMap();
    virtual ~TileMap();
public:
    bool init(int32_t width, int32_t height, float tilesize);
    void setImageDescriptor(const std::string &file);
    std::string getImageDescriptor()const;

    virtual TileMapType getTileMapType()const;
public:
    void accept(std::function<void(int32_t x, int32_t y, std::shared_ptr<Tile> tile)> visitor);
    void accept(TileVisitor *visitor);

    void accpetEdge(std::function<void(const sf::Vector2i &a, const sf::Vector2i &b)> visitor);
public:
    float getTileSize()const;
    sf::Vector2i getTileMapSize()const;

    std::shared_ptr<Tile> getTileByIndex(const sf::Vector2i &index);
    std::shared_ptr<Tile> getTileByIndex(int32_t x, int32_t y);

    void setTextVisible(bool visible);
    bool isTextVisible()const;
public:
    virtual std::vector<sf::Vector2i> getAdjacentTileByTileIndex(int32_t x, int32_t y) = 0;
    virtual std::optional<sf::Vector2i> getAdjacentTileByDirection(int32_t x, int32_t y,
                                                                   TileDirection direction) = 0;
    virtual sf::Vector2f getWorldPositionByTileIndex(int32_t x, int32_t y) = 0;
    virtual sf::Vector2i getTileIndexByWorldPosition(int32_t x, int32_t y) = 0;

    boost::signals2::signal<void(int32_t, int32_t)> tileClicked;
public:
    virtual PTree serialize() override;
    virtual void deserialize(const PTree &node) override;
protected:
    virtual std::shared_ptr<Tile> createTile(int32_t i, int32_t j, float tilesize) = 0;
    void onDrawNode(sf::RenderTarget &target, sf::RenderStates states) const override;
protected:
    std::unique_ptr<class TileMapData> data;
};

using TileMapPointer = std::shared_ptr<TileMap>;

#define GET_TILE_X(tilePosition) (tilePosition >> 16)
#define GET_TILE_Y(tilePosition) (tilePosition & 0xffff)
#define GET_TILE_POSITION(x,y)    (x << 16 | y)
